import logging

from HTMLParser import HTMLParser, HTMLParseError
from operator import itemgetter
from xml.parsers import expat

import config as c
import datetime as dtm
import request as r
import tools as t

class IndxParser(HTMLParser):
    """Confirm a successful login to the TiVo TCO site."""
    def __init__(self):
        #Initialize various components.
        HTMLParser.__init__(self)
        self.log = logging.getLogger("TiVoToDo.parsers.IndxParser")
        self.title_flag = False
        self.span_flag = True
        self.get_data = False
        self.select_flag = False
        self.channelId = ''
        self.channel = ''

    # Inspect HTML start tags and attributes.
    def handle_starttag(self, tag, attrs):
        # Assess all span tags.
        if tag == 'span' and self.span_flag:

            # The first span tag does not have the greeting attr.
            if attrs[0][1] != 'greeting':
                log.info('%s' % attrs[0][1])
                log.info('\nERROR: Login failure.\n' +
                         'Please check email and password in TiVoToDo.conf.\n')
                log.info('%s' % self.title)
                x = raw_input("\nPress Enter to exit")
                sys.exit (1)

        # Assess all title tags.
        if tag == 'title':
            self.title_flag = True
            self.get_data = True

        # Assess all select tags.
        if tag == 'select' and attrs:

            if attrs[0][1] == 'channelId':
                self.select_flag = True
                self.get_data = True

        # Assess all option tags.
        if tag == 'option' and self.select_flag:
            self.channelId = attrs[0][1]

    # Inspect HTML DATA.
    def handle_data(self, data):
        # Relevant data is expected.
        if self.get_data:

            if self.title_flag:
                self.title = data
                self.title_flag = False
                self.get_data = False

            if self.select_flag:
                self.channel = data

    # Inspect HTML end tags.
    def handle_endtag(self, tag):
        # A proper span tag has been found.
        if tag == 'span':
            self.span_flag = False

        # A proper option tag has been found.
        if tag == 'option' and self.select_flag:
            c.clu.append([c.rdi_tivo, self.channel, self.channelId])
            c.tivo_clu.append([c.rdi_tivo,
                               self.channel.split(' ',1)[0],
                               self.channel.split(' ',1)[1]])

        # A proper select tag has been found.
        if tag == 'select' and self.select_flag:
            self.select_flag = False
            self.get_data = False

class ToDoParser(HTMLParser):
    """Get the required information from the To Do List page."""
    def __init__(self):
        #Initialize various components.
        HTMLParser.__init__(self)
        self.log = logging.getLogger("TiVoToDo.parsers.HTMLParser")
        self.html_error = False
        self.ul_flag = False
        self.span_flag = False
        self.span_tag = None
        self.span_time = ['day',
                          'date',
                          'time']
        self.span_type = ['icon i_check',
                          'icon i_doublechecks',
                          'icon i_wish']
        self.a_data = ''
        self.a_flag = False
        self.processed = False
        self.valid_date = False

    # Inspect HTML start tags and attributes.
    def handle_starttag(self, tag, attrs):
        # Assess all unordered lists.
        if tag == 'ul' and attrs:

            # Flag unordered list containing recording type, day, date & time.
            if attrs[0][1] == 'dynamicTrigger':
                self.ul_flag = True

        # Assess all division tags.
        if tag == 'div' and attrs:

            # Look for the end of the ToDo List for this TiVo.
            if attrs[0][1] == 'noShows':
                c.noShows = True

        # Assess all span tags.
        if tag == 'span' and attrs:

            # Set the show's recording type.
            if attrs[0][1] in self.span_type:

                if attrs[0][1] == 'icon i_check':
                    c.rdi_rtyp = 'Explicit'

                if attrs[0][1] == 'icon i_doublechecks':
                    c.rdi_rtyp = 'Season Pass'

                if attrs[0][1] == 'icon i_wish':
                    c.rdi_rtyp = 'Wish List'

                self.log.debug('c.rdi_rtyp = %s' % c.rdi_rtyp)

            # Set toggle to get this show's weekday, date, and time.
            if attrs[0][1] in self.span_time:
                self.span_flag = True
                self.span_tag = attrs[0][1]

        # Assess all anchor tags.
        if tag == 'a' and attrs and self.valid_date:
            # Find TiVo's codes for show details and episode details.
            if attrs[0][1] == 'trigger':
                self.log.info('Working on To Do List item: %s %s.' %\
                               (c.rdi_date, c.rdi_time))
                self.a_flag = True
                c.rdi_edtl = attrs[3][1].split('&')[1].split('=')[1].split('.')[1]
                c.rdi_sdtl = attrs[3][1].split('&')[2].split('=')[1].split('.')[1]
                self.log.debug('c.rdi_edtl = %s' % c.rdi_edtl)
                self.log.debug('c.rdi_sdtl = %s' % c.rdi_sdtl)

                # Loop through the shows dictionary keys.
                for key in c.shows.iterkeys():

                    # Test whether the show has already been found.
                    if c.rdi_ttsn in key:

                        if c.rdi_sdtl == c.shows[key][5] and \
                           c.rdi_edtl == c.shows[key][6]:
                            self.processed = True
                            c.rdi_skey = key
                            self.log.debug('c.rdi_skey = %s' % c.rdi_skey)

                # Show unknown, call the EpisodeParser.
                if not self.processed:
                    c.showId = ('collectionId=tivo:cl.' + c.rdi_sdtl +
                                '&contentId=tivo:ct.' + c.rdi_edtl)
                    parser = EpisodeParser()
                    c.noData = False
                    offset = 0
                    while not c.noData:
                        try:
                            url = c.base_url + c.show_url + \
                                  c.showId + '&offset=%d' % offset
                            offset +=10
                            login_data = None
                            r.request_url(url, parser, login_data)

                        # Account for any HTML indexing errors.
                        except IndexError:
                            t.html_debug()
                            self.log.info('An indexing error has occurred.\n \
                                           Please open (or add to) an issue at: \
                                           http://code.google.com/p/tivotodo/issues/list.\n\
                                           Please attach "Debug.html" to the issue. Thanks!\n')

                        # Account for any XML errors.
                        except expat.ExpatError:
                            t.xml_debug()
                            self.log.info('An XML error has occurred. \n\
                                           Please open (or add to) an issue at: \
                                           http://code.google.com/p/tivotodo/issues/list. \n\
                                           Please attach "Debug.xml" to the issue. Thanks!\n')

    # Inspect HTML DATA.
    def handle_data(self, data):
        # Collect the weekday, date, and time from the DATA fields.
        if self.span_flag:

            # Collect the weekday data for display.
            if self.span_tag == 'day':
                c.rdi_wday = data
                self.log.debug('c.rdi_wday = %s' % c.rdi_wday)

            # Collect and format the date for proper handling.
            if self.span_tag == 'date':
                a = data.partition('/')
                c.rdi_date = (a[0].rjust(2,'0') +
                              a[1] +
                              a[2].rjust(2,'0'))
                self.log.debug('c.rdi_date = %s' % c.rdi_date)

            # Collect and format the time for proper handling.
            if self.span_tag == 'time':
                c.rdi_time = data.rjust(8,'0')
                c.rdi_dttm = t.str2date(c.this_year,
                                        c.rdi_date,
                                        c.rdi_time)
                if c.rdi_dttm - c.now < dtm.timedelta(days = -2):
                    c.rdi_dttm = t.str2date(str(int(c.this_year) + 1),
                                            c.rdi_date,
                                            c.rdi_time)
                self.valid_date = c.now < c.rdi_dttm
                self.log.debug('c.rdi_time = %s' % c.rdi_time)
                self.log.debug('c.rdi_dttm = %s' % c.rdi_dttm)

    # Inspect HTML end tags.
    def handle_endtag(self, tag):
        # Always toggle off these flags.
        self.span_flag = False

        # End of unordered list.
        if tag == 'ul':
            self.ul_flag = False

        # End of current show data, commit to master list and dictionary.
        if tag == 'li' and self.ul_flag and self.valid_date:

            # Commit common items to shows dictionary.
            if c.rdi_skey not in c.shows:
                c.shows[c.rdi_skey] = (c.rdi_titl,  # [0] Show title
                                       c.rdi_dura,  # [1] Show duration
                                       c.rdi_lchn,  # [2] Long channel name
                                       c.rdi_spol,  # [3] SP original link
                                       c.rdi_schd,  # [4] Schedule
                                       c.rdi_sdtl,  # [5] Show detail ID
                                       c.rdi_edtl,  # [6] Episode detail ID
                                       c.rdi_cdtl,  # [7] Channel detail ID
                                       c.rdi_spoc)  # [8] SP "other channels"
                self.log.info('Added %s"%s" to cache.' % \
                               (c.rdi_rtyp.center(12, ' '), c.rdi_titl))

            # Retrieve common items from shows dictionary.
            else:
                c.rdi_titl = c.shows.get(c.rdi_skey)[0]
                c.rdi_dura = c.shows.get(c.rdi_skey)[1]
                c.rdi_lchn = c.shows.get(c.rdi_skey)[2]
                c.rdi_spol = c.shows.get(c.rdi_skey)[3]
                c.rdi_schd = c.shows.get(c.rdi_skey)[4]
                c.rdi_sdtl = c.shows.get(c.rdi_skey)[5]
                c.rdi_edtl = c.shows.get(c.rdi_skey)[6]
                c.rdi_cdtl = c.shows.get(c.rdi_skey)[7]
                c.rdi_spoc = c.shows.get(c.rdi_skey)[8]

            # Commit both unique and common items to Raw Data list.
            t.update_rdl(c.rdi_dura)
            # Commit to cache file.
            t.write_cache()
            # Create an rdl.csv file for inspection purposes.
            t.make_csv(c.rdl, 'rdl.csv')
            self.processed = False
            self.log.debug('c.rdi_titl = %s \n' % c.rdi_titl)

class EpisodeParser(HTMLParser):
    """Look for alternative show information."""
    # Initialize various components.
    def __init__(self):
        HTMLParser.__init__(self)
        self.log = logging.getLogger("TiVoToDo.parsers.EpisodeParser")
        self.h2_data = ''
        self.h2_flag = False
        self.div_data = ''
        self.div_flag = False
        self.div_list = []
        self.a_flag = False
        self.p_flag = False
        self.span_flag = False
        self.found = False
        self.get_data = False
        self.rec_opt = False
        self.td_data = ''
        self.td_date = False
        self.td_desc = False
        self.td_chan = False

    # Inspect HTML start tags and attributes.
    def handle_starttag(self, tag, attrs):
        # A long channel has yet to be found.
        if not c.noData:

            # Assess all second level heading tags.
            if tag == 'h2' and attrs:

                # Identify section to get the full title of the show.
                if attrs[0][1] == 'tco_findtv_showdetails':
                    self.h2_flag = True
                    self.get_data = True

            # Assess all division tags.
            if tag == 'div' and attrs:

                # Identify section to get upcoming episodes.
                if attrs[0][1] == 'future':
                    self.div_flag = True

                # Identify section that immediately follows upcoming episodes.
                if attrs[0][1] == 'aboutThis':
                    self.div_flag = False

                # Identify section to get long channel information.
                if attrs[0][1] == 'channelDetailsRight':
                    self.div_flag = True

            # Assess all paragraph tags.
            if tag == 'p' and self.div_flag:

                if self.div_flag:
                    self.p_flag = True
                    self.get_data = True

                if attrs:
                    # End of upcoming episodes.
                    if attrs[0][1] == 'noData':
                        c.noData = True

            # Assess all span tags
            if tag == 'span' and attrs:

                # Account for shows with "May not record" warning
                if attrs[0][1] == 'alert':
                    self.span_flag = True
                    self.get_data = True
                
                # Look for New Show indicator
                if attrs[0][1] == 'isNEW':
                    c.rdi_new = True

            # Assess all anchor tags.
            if tag == 'a' and attrs:

                if attrs[0][0] == 'href':

                    # Identify long channel name.
                    if len(attrs) == 1 and self.td_chan:
                        self.get_data = True

                    # Identify 'May not record' popup link.
                    if len(attrs) > 1 and self.div_flag:
                        if attrs[1][1] == 'popup':
                            self.get_data = False

                # Identify a hyperlink with the correct date.
                if (attrs[0][0] == 'href' and self.found and
                    self.td_desc and not self.rec_opt):
                    a = attrs[0][1].partition('?')
                    a = a[2].partition('&')
                    a = a[0].split('.')
                    c.rdi_cdtl = a[2]
                    c.rdi_schd = a[5]
                    c.rdi_dura = int(a[6]) / 60
                    c.rdi_spol = attrs[0][1].split('=', 1)[1]
                    c.rdi_skey = ''.join([str(c.rdi_sdtl), '+',
                                          str(c.rdi_dura), '+',
                                          str(c.rdi_cdtl), '+',
                                          str(c.rdi_ttsn)])
                    self.log.debug('c.rdi_cdtl = %s' % c.rdi_cdtl)
                    self.log.debug('c.rdi_schd = %s' % c.rdi_schd)
                    self.log.debug('c.rdi_dura = %s' % c.rdi_dura)
                    self.log.debug('c.rdi_spol = %s' % c.rdi_spol)
                    self.log.debug('c.rdi_skey = %s' % c.rdi_skey)

                    # Key not in shows dictionary, call RecXParser.
                    if c.rdi_skey not in c.shows:
                        parser = RecXParser()
                        login_data = None
                        r.request_url(c.base_url +
                                      c.reco_url +
                                      c.rdi_spol,
                                      parser, login_data)
                        c.rdi_spoc = RecParser.option_list
                        self.log.debug('c.rdi_spoc = %s' % c.rdi_spoc)
                        RecParser.option_list = []
                    self.rec_opt = True

            # Assess all table data tags.
            if tag == 'td' and attrs and self.div_flag:

                # Identify section to get future airings date.
                if attrs[0][1] == 'tco_programInfo_epidsodesDate':
                    self.get_data = True
                    self.td_date = True

                # Identify section to get episode information.
                if attrs[0][1] == 'tco_programInfo_episodesDes':
                    self.td_desc = True

                # Identify section to get long channel information.
                if attrs[0][1] == 'tco_programInfo_episodesChannel':
                    self.td_chan = True
                    self.get_data = True

    # Inspect HTML DATA.
    def handle_data(self, data):
        # Relevant data is expected.
        if self.get_data and not c.noData:

            # Collect full show title.
            if self.h2_flag:
                self.h2_data = self.h2_data + data

            # Collect long channel information.
            if self.p_flag and data.strip() != '':
                self.div_list.append(data.strip())

            # A matching date has not yet been found.
            if not self.found:
                # Collect show's date.
                if self.td_date:
                    self.td_data += data

            # A matching date has been found.
            if self.found:
                # Collect show's long channel.
                if self.td_chan:
                    if data.strip() == c.rdi_lchn:
                        c.noData = True

    # Inspect HTML end tags.
    def handle_endtag(self, tag):
        # A matching date has not yet been found.
        if not c.noData:

            # Got the full title.
            if tag == 'h2' and self.h2_flag:
                c.rdi_titl = (self.h2_data.rstrip('\n ')).lstrip()
                self.get_data = False
                self.h2_data = ''
                self.h2_flag = False
                self.log.debug('c.rdi_titl = %s' % c.rdi_titl)

            # Accounted for "May not record" warning
            if tag == 'span' and self.p_flag:
                self.get_data = True
                self.span_flag = False

            # Assess all paragraph tags.
            if tag == 'p' and self.div_flag:
                self.get_data = False
                self.div_flag = False
                self.p_flag = False
                c.rdi_lchn = self.div_list[1]
                self.log.debug('c.rdi_lchn = %s' % c.rdi_lchn)

            # A futute episode's date has been found.
            if tag == 'td' and self.td_date and not self.found:
                self.td_date = False
                self.get_data = False
                week = dtm.timedelta(days=7)
                a = self.td_data.split()
                year = a[0].split('/')[2]
                date = a[0].split('/')[0].rjust(2,'0') + '/' + \
                       a[0].split('/')[1].rjust(2,'0')
                time = a[1] + ' ' + a[2]
                self.td_data = ''

                # A matching date has not been found.
                if not self.found:

                    # Look for an exact date and time match.
                    if c.rdi_dttm == t.str2date(year, date, time):
                        self.found = True

                    # Look for an exact time match 7 days in the future.
                    elif c.rdi_dttm + week == t.str2date(year, date, time):
                        self.found = True

                    # Look for an exact time match 7 days in the past.
                    elif c.rdi_dttm - week == t.str2date(year, date, time):
                        self.found = True

            # A future episode's long channel has been found.
            if tag == 'a' and self.td_chan:
                self.td_chan = False
                self.get_data = False

class RecXParser:
    """Get CDATA information from returned Recording options xml."""
    # Initialize various components.
    def __init__(self):
        self.log = logging.getLogger("TiVoToDo.parsers.RecXParser")
        self._parser = expat.ParserCreate()
        self._parser.StartElementHandler = self.start
        self._parser.EndElementHandler = self.end
        self._parser.CharacterDataHandler = self.data
        self.get_data = False
        self.out_data = ''

    # expat.Parser required definition.
    def feed(self, data):
        self._parser.Parse(data, 0)

    # expat.Parser required definition.
    def close(self):
        self._parser.Parse("", 1)
        del self._parser

    # Inspect all xml start tags and attrs.
    def start(self, tag, attrs):
        # Identify section with relevant data.
        if tag == 'content':
            self.get_data = True

    # Inspect all xml end tags.
    def end(self, tag):
        # All data found, pass everything to RecParser.
        if tag == 'content':
            self.get_data = False
            parser = RecParser()
            parser.feed(self.out_data)
            parser.close

    # Account for malformed tags.
    def data(self, data):
        if self.get_data:
            data = data.replace('</br/>', '<br/>')
            self.out_data = self.out_data + data.encode('utf-8')

    # expat.Parser required definition.
    def reset(self):
        pass

class RecParser(HTMLParser):
    """Parse "xml" returned from Season Pass recording options call."""
    option_list = []
    # Initialize various components.
    def __init__(self):
        HTMLParser.__init__(self)
        self.log = logging.getLogger("TiVoToDo.parsers.RecParser")
        self.div_data = ''
        self.div_flag = False
        self.form_flag = False
        self.get_data = False
        self.option_data = ''
        self.option_dict = {}
        self.select_flag = False

    # Inspect HTML start tags and attributes.
    def handle_starttag(self, tag, attrs):
        # Identify section with relevant data.
        if tag == 'form' and attrs:

            # Found correct section.
            if attrs[0][1] == 'rflowOptionForm':
                self.form_flag = True

        # Differentiate the different recording types.
        if tag == 'div' and attrs and self.form_flag:

            # Explicit (repeat/movie).
            if attrs[0][1] == 'explicit' and c.rdi_rtyp == 'Explicit':
                self.div_flag = True

            # Recurring recording (Season Pass/Wish List).
            if attrs[0][1] == 'seasonpass' and \
               c.rdi_rtyp != 'Explicit':
                self.div_flag = True

        # Identify section with additional channel offerings.
        if tag == 'select' and attrs and self.form_flag and self.div_flag:

            # Found the correct section.
            if attrs[0][1] == 'offerId':
                self.select_flag = True

        # Set flag to gather the additional channel offering information.
        if tag == 'option' and attrs and self.select_flag:
            self.option_data = attrs[0][1]
            self.get_data = True

    # Inspect HTML DATA.
    def handle_data(self, data):
        # Collect additional channel information.
        if self.select_flag and self.get_data:
            self.option_dict[self.option_data] = data.rjust(20,' ')

    # Inspect HTML end tags.
    def handle_endtag(self, tag):
        self.get_data = False

        # All additional channels gathered.
        if tag == 'select' and self.select_flag:
            self.select_flag = False
            self.div_flag = False
            self.form_flag = False
            self.option_list.append(sorted(self.option_dict.items(),
                                           key = itemgetter(1)))

class SPMParser(HTMLParser):
    # Initialize various components.
    def __init__(self):
        HTMLParser.__init__(self)
        self.log = logging.getLogger("TiVoToDo.parsers.SPMParser")
        self.get_data = False
        self.td_priority = False
        self.span_title = False
        self.span_station = False
        self.span_data = ''

    # Inspect HTML start tags and attributes.
    def handle_starttag(self, tag, attrs):
        if tag == 'td' and attrs:

            # Identify Season Pass Priority.
            if attrs[0][1] == 'priority':
                self.get_data = True
                self.td_priority = True

        if tag == 'span' and attrs:

            # Identify show channel.
            if attrs[0][1] == 'showtitle':
                self.get_data = True
                self.span_title = True

            if attrs[0][1] == 'chstation':
                self.get_data = True
                self.span_station = True

        if tag == 'input' and attrs:

            # Identify show subscription ID.
            if attrs[0][1] == 'subscriptionIds':
                self.log.debug('c.spm_sIds = %s' % c.spm_sIds)
                c.spm_sIds = attrs[1][1]
                c.spm.append([c.spm_prty,
                              c.spm_titl,
                              c.spm_chan,
                              c.spm_sIds,
                              c.rdi_tivo,
                              c.rdi_ttsn])

    # Inspect HTML DATA.
    def handle_data(self, data):
        if self.get_data == True:

            if self.td_priority:
                self.log.debug('c.spm_prty = %s' % c.spm_prty)
                c.spm_prty = data
                self.get_data = False
                self.td_priority = False

            if self.span_station or self.span_title:
                data = data.replace('\n', '')
                data = data.replace('\t', '')
                data = data.strip()
                self.span_data = ' '.join([self.span_data, data])

    # Inspect HTML end tags.
    def handle_endtag(self, tag):
        if tag == 'span' and self.span_title:
            self.span_title = False
            self.get_data = False
            c.spm_titl = self.span_data.strip().replace('  ',' ')
            self.log.debug('c.spm_titl = %s' % c.spm_titl)
            self.span_data = ''

        if tag == 'td' and self.span_station:
            self.span_station = False
            self.get_data = False
            c.spm_chan = self.span_data.strip().replace('  ',' ')
            self.log.debug('c.spm_chan = %s' % c.spm_chan)
            self.span_data = ''

class SearchParser(HTMLParser):
    # Initialize various components.
    def __init__(self):
        HTMLParser.__init__(self)
        self.log = logging.getLogger("TiVoToDo.parsers.SearchParser")
        self.get_data = False
        self.match = False
        self.title = ''
        self.div_content = False
        self.next_episode = False

    # Inspect HTML start tags and attributes.
    def handle_starttag(self, tag, attrs):
        if tag == 'div' and attrs:
            if attrs[0][0] == 'id' and attrs[0][1] == 'page-content':
                self.div_content = True
            if attrs[0][1] == 'bubble tco_AdvancedSearchResultsListing':
                self.log.info('Show found!')
            if attrs[0][1] == 'bubble tco_AdvancedSearchResultsListing searchResultMessage':
                self.log.info('Show not found!')

        if tag == 'input' and attrs and self.div_content:
            # self.log.info('attrs[0][1] = %s, attrs[1][1] = %s' % (attrs[0][1], attrs[1][1]))
            if attrs[0][1] == 'hidden' and attrs[1][1] == 'searchTitle':
                self.log.info('%s -- %s' % (attrs[2][1], c.spm_titl))
                self.title = attrs[2][1].strip()
                self.match = self.title == c.spm_titl
                self.log.info('Match = %s' % self.match)
                self.div_content = False

        if tag == 'p' and attrs:
            if attrs[0][1] == 'tco_AdvancedResults_firstPara':
                self.next_episode = True

        if tag == 'a' and self.next_episode:
            t_schd = attrs[0][1].split('.')[6]
            t_schd = t_schd.split('-')
            t_schd = dtm.datetime(int(t_schd[0]), int(t_schd[1]), 
                                  int(t_schd[2]), int(t_schd[3]), 
                                  int(t_schd[4]), int(t_schd[5]))
            self.next_episode = False
            for i in range(len(c.rdl)):
                o_schd = c.rdl[i][16]
                o_schd = o_schd.split('-')
                o_schd = dtm.datetime(int(o_schd[0]), int(o_schd[1]), 
                                      int(o_schd[2]), int(o_schd[3]), 
                                      int(o_schd[4]), int(o_schd[5]))
                if t.check_time(t_schd) == t.check_time(o_schd) \
                   and t_schd.year == o_schd.year \
                   and t_schd.month == o_schd.month \
                   and t_schd.day == o_schd.day \
                   and c.rdl[i][12] != self.title: # \
                    # self.log.info('"%s" -- "%s"' % (t_schd, o_schd))
                   # and c.rdl[i][4] == 0:
                    self.log.info('%s %s %s - %s' % (c.rdl[i][0],
                                                     c.rdl[i][1],
                                                     c.rdl[i][2],
                                                     c.rdl[i][12]))

    # Inspect HTML DATA.
    def handle_data(self, data):
        pass

    # Inspect HTML end tags.
    def handle_endtag(self, tag):
        pass
